/*
 * Neuron.java
 *
 * Created on Dec 1, 2011
 *
 * Copyright (c) 2011 Artur Rataj.
 *
 * This code is distributed under the terms of the GNU Library
 * General Public License, either version 3 of the license or, at
 * your option, any later version.
 */

package pl.gliwice.iitis.spatialnn.network;

import java.util.*;

import pl.gliwice.iitis.graphutils.Point;
import pl.gliwice.iitis.graphutils.view.Drawable;

/**
 * A neuron.
 * 
 * @author Artur Rataj
 */
public class Neuron extends Component implements Drawable {
    /*
     * A network, to which this neuron belongs.
     */
    protected Network network;
    /**
     * Unique key of this neuron, can not be negative.
     */
    public int key;
    /**
     * Type of this neuron.
     */
    public NeuronType type;
    /**
     * This neuron's number of the layer in the signal processing path.
     */
    public int layerNum;
    /**
     * Location of this neuron.
     */
    public Point loc;
    /**
     * Forward angle of inputs of this neuron, as defined in <code>Wood</code>.
     */
    public double forwardAngle;
//    /**
//     * Values of subsequent expressions.
//     */
//    public double[] expression;
    /**
     * Input neurons or membranes, empty array for none. If the inputs are
     * <code>InputType.LEFT</code> or <code>InputType.RIGHT</code>,
     * then their indices are respectively 0 and 1.
     */
    public AbstractTarget[] inputs;
    /**
     * Output neurons or membranes, empty list for none.
     */
    public AbstractTarget[] output;
    /**
     * Relative locations of inputs, as returned for neuron types <code>MINP</code>
     * or <code>MAXP</code>; for other types, this parameter is null.<br>
     * 
     * Computed in <code>sync()</code>.
     */
    double[] inputLoc;
    
    /**
     * Creates a new instance of <code>Neuron</code>.
     * 
     * @param network network, to which this neuron belongs
     * @param key this neuron's unique identifier
     * @param type type
     * @param layerNum layer number
     * @param loc location of this neuron
     * @param forwardAngle forward angle of inputs of this neuron,
     * as defined in <code>Wood</code>
     */
    public Neuron(Network network, int key, NeuronType type,
            int layerNum, Point loc, double forwardAngle) {
        this.network = network;
        if(key < 0)
            throw new RuntimeException("negative key");
        this.key = key;
        this.type = type;
        this.layerNum = layerNum;
        this.loc = loc;
        this.forwardAngle = forwardAngle;
        inputs = new AbstractTarget[0];
        output = new AbstractTarget[0];
    }
    @Override
    public void addInput(AbstractTarget target) {
        AbstractTarget[] a = new AbstractTarget[inputs.length + 1];
        System.arraycopy(inputs, 0, a, 0, inputs.length);
        a[inputs.length] = target;
        inputs = a;
    }
    @Override
    public void removeInput(int index) {
        AbstractTarget[] newInputs = new AbstractTarget[inputs.length - 1];
        for(int j = 0; j < newInputs.length; ++j)
            if(j < index)
                newInputs[j] = inputs[j];
            else
                newInputs[j] = inputs[j + 1];
        inputs = newInputs;
    }
    @Override
    public void removeOutputComponent(int index) {
        AbstractTarget[] newOutput = new AbstractTarget[output.length - 1];
        for(int j = 0; j < newOutput.length; ++j)
            if(j < index)
                newOutput[j] = output[j];
            else
                newOutput[j] = output[j + 1];
        output = newOutput;
    }
    @Override
    public AbstractTarget[] getInputs() {
        return inputs;
    }
    @Override
    public AbstractTarget[] getOutputComponents() {
        return output;
    }
    /**
     * Adss a component to the output.
     * 
     * @param target target of the output neuron to add
     */
    public void addOutputComponent(AbstractTarget target) {
        AbstractTarget[] a = new AbstractTarget[output.length + 1];
        System.arraycopy(output, 0, a, 0, output.length);
        a[output.length] = target;
        output = a;
    }
    /**
     * If this neuron has a valid number of inputs.
     * 
     * @return if the number of inputs is within the range of this neuron's
     * type
     */
    public boolean validInputNum() {
        if(type.getMinInNum() != -1 &&
                inputs.length < type.getMinInNum())
            return false;
        if(type.getMaxInNum() != -1 &&
                inputs.length > type.getMaxInNum())
            return false;
        return true;
    }
    /**
     * If this neuron has a valid number of outputs. A neuron can only
     * have zero or a single output. A number of neurons can be added to the
     * output, if any.
     * 
     * @return if it is not so, that the neuron type can not have an output,
     * but some output neurons are connected
     */
    public boolean validOutputNum() {
        if(!type.getOutPossible() && output.length != 0)
            return false;
        return true;
    }
    @Override
    public void sync(Network network) {
        final double EPSILON = 1e-6;
        int num = inputs.length;
        inputLoc = new double[num];
        for(int i = 0; i < num; ++i) {
            Point in = inputs[i].getLoc(network);
            double value;
            if(forwardAngle == 0.0)
                value = in.x /* - loc.x */;
            else if(Math.abs(forwardAngle - Math.PI/2.0) < EPSILON)
                value = in.y /* - loc.y */;
            else
                throw new RuntimeException("unsupported forward angle");
            inputLoc[i] = value;
        }
    }
    /**
     * Propagates a signal through this neuron.
     * 
     * @param in values of the signal; the array must match the number of inputs
     * @return an output value
     */
    public double propagate(double[] in) {
        if(in.length != inputs.length)
            throw new RuntimeException("mismatch of number of inputs");
        return type.get(in, inputs.length, inputLoc);
    }
    @Override
    public String toString() {
        return "neuron " + key + " " +
                layerNum + ("" + type.getSymbolChar()).trim();
    }
}
